/*
 * VertexBufferImpl.h
 *
 *  Created on: 2011/06/23
 *      Author: naoki
 */

#ifndef INCLUDED_NTENGINE_GRAPHICS_VERTEXBUFFERIMPL_H
#define INCLUDED_NTENGINE_GRAPHICS_VERTEXBUFFERIMPL_H

#include "NTEngine/NTEngine.h"
#include "NTEngine/Graphics/VertexBuffer.h"
#include "NTEngine/Base/ReferenceCounter.h"
#include "NTEngine/Base/Array.h"
#include "NTEngine/Xml/Element.h"
#include "NTEngine/Xml/Attribute.h"
#include "NTEngine/Math/Matrix3x4.h"
#include "NTEngine/Base/RefString.h"

#include <GLES2/gl2.h>

namespace NTEngine {
namespace Graphics {

class VertexBuffer::Impl : public ReferenceCounter {
public:
	// --------------------------------------------------------
	Impl( Xml::ConstElement e )
	: mObject( 0 )
	, mVertexNumber( 0 )
	, mFormat( VertexBuffer::FORMAT_VERTEX )
	, mManaged( VertexBuffer::MANAGED_STATIC )
	, mBuffer( 0 )
	, mName( 0 ) {
		//エラーチェック全然してません
		mVertexNumber = e.childNumber();
		//名前とフォーマットを抜く
		int an = e.attributeNumber();
		for ( int i = 0; i < an; ++i ){
			Xml::ConstAttribute a = e.attribute( i );
			RefString name( a.name() );
			if ( name == "name" ){
				RefString name( a.value() );
				mName = name.copyOriginal();
			} else if ( name == "managed" ) {
				RefString managed( a.value() );
				if ( managed == "static" ) {
					mManaged = VertexBuffer::MANAGED_STATIC;
				} else if ( managed == "dynamic" ) {
					mManaged = VertexBuffer::MANAGED_DYNAMIC;
				}
			}
		}
		if ( mVertexNumber > 0 ){
			createObject();
			if ( mFormat == VertexBuffer::FORMAT_VERTEX ) {
				fillBufferVertex( e );
			} else if ( mFormat == VertexBuffer::FORMAT_BLENDINGVERTEX ) {
				fillBufferBlendingVertex( e );
			}
		}
	}

	// --------------------------------------------------------
	Impl( int vertexNumber, VertexBuffer::Format fmt, VertexBuffer::Managed mng )
	: mObject( 0 )
	, mVertexNumber( vertexNumber )
	, mFormat( fmt )
	, mManaged( mng )
	, mBuffer( 0 )
	, mName( 0 ) {
		createObject();
	}

	// --------------------------------------------------------
	~Impl() {
		SAFE_DELETE_ARRAY( mName );
		SAFE_DELETE_ARRAY( mBuffer );
		glDeleteBuffers( 1, &mObject );
		mObject = 0;
	}

	// --------------------------------------------------------
	void* lock() {
		if ( mManaged == VertexBuffer::MANAGED_STATIC ) {
			// 静的バッファならここで生成を行う
			ASSERT( mBuffer == 0 );
			mBuffer = NEW unsigned char[ VERTEX_SIZES[ mFormat ] * mVertexNumber ];
		}
		return mBuffer;
	}

	// --------------------------------------------------------
	void unlock() {
		ASSERT( mBuffer ); // 管理方法がどうであってもバッファはあるよね？
		glBindBuffer( GL_ARRAY_BUFFER, mObject );
		glBufferSubData( GL_ARRAY_BUFFER, 0, VERTEX_SIZES[ mFormat ] * mVertexNumber, mBuffer );
		if ( mManaged == VertexBuffer::MANAGED_STATIC ) {
			SAFE_DELETE_ARRAY( mBuffer );
		}
		glBindBuffer( GL_ARRAY_BUFFER, 0 );
	}

	// --------------------------------------------------------
	void setInputLayout() {
//		glBindBuffer( GL_ARRAY_BUFFER, mObject );
		SET_INPUT_LAYOUT[ mFormat ]();
	}

	// --------------------------------------------------------
	void createObject() {
		// バッファオブジェクトをもらう
		glGenBuffers( 1, &mObject );
		// 未初期化でバッファを予約しておく
		glBindBuffer( GL_ARRAY_BUFFER, mObject );
		const GLint bufferSize = VERTEX_SIZES[ mFormat ] * mVertexNumber;
		glBufferData( GL_ARRAY_BUFFER, bufferSize, 0, GL_STATIC_DRAW );
		// バッファサイズを検証
		GLint allocatedBufferSize = 0;
		glGetBufferParameteriv( GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &allocatedBufferSize );
		HARD_ASSERT( bufferSize == allocatedBufferSize );
		// 動的バッファならここで生成を行う
		if ( mManaged == VertexBuffer::MANAGED_DYNAMIC ) {
			ASSERT( mBuffer == 0 );
			mBuffer = NEW unsigned char[ VERTEX_SIZES[ mFormat ] * mVertexNumber ];
		}
	}

	// --------------------------------------------------------
	void fillBufferVertex( Xml::ConstElement e ) {
		Vertex* vertices = static_cast< Vertex* >( lock() );
		for ( int i = 0; i < mVertexNumber; ++i ){
			Xml::ConstElement vertex = e.child( i );
			//アトリビュートから抜き出す
			int an = vertex.attributeNumber();
			//デフォルト値充填
			Vertex& v = vertices[ i ];
			v.p.w = 1.f;
			v.n.set( 0.f, 0.f, 0.f );
			v.c = 0xffffffff;
			v.t.set( 0.f, 0.f );
			for ( int j = 0; j < an; ++j ){
				Xml::ConstAttribute a = vertex.attribute( j );
				RefString name( a.name() );
				if ( name == "position" ){
					a.getFloatValues( &v.p.x, 3 );
				}else if ( name == "normal" ){
					a.getFloatValues( &v.n.x, 3 );
				}else if ( name == "uv" ){
					a.getFloatValues( &v.t.x, 2 );
				}else if ( name == "color" ) {
					//RGBA32形式カラーに変換
					float color[ 4 ];
					a.getFloatValues( color, 4 );
					unsigned c[ 4 ];
					for ( int i = 0; i < 4; ++i ){
						c[ i ] = static_cast< unsigned >( color[ i ] * 255.f + 0.5f );
						c[ i ] = ( c[ i ] < 0.f ) ? 0 : c[ i ];
						c[ i ] = ( c[ i ] > 255 ) ? 255 : c[ i ];
					}
					//RGBAの順でファイルに書かれているわけだが、32bit形式だとARGBになる。注意。
					v.c = ( c[ 3 ] << 24 ) | ( c[ 0 ] << 16 ) | ( c[ 1 ] << 8 ) | c[ 2 ];
				}
			}
		}
		unlock();
		vertices = 0;
	}
	// --------------------------------------------------------
	void fillBufferBlendingVertex( Xml::ConstElement e ) {
		BlendingVertex* vertices = static_cast< BlendingVertex* >( lock() );
		for ( int i = 0; i < mVertexNumber; ++i ){
			Xml::ConstElement vertex = e.child( i );
			//アトリビュートから抜き出す
			int an = vertex.attributeNumber();
			//デフォルト値充填
			BlendingVertex& v = vertices[ i ];
			v.p.w = 1.f;
			v.n.set( 0.f, 0.f, 0.f );
			v.c = 0xffffffff;
			v.t.set( 0.f, 0.f );
			for ( int j = 0; j < an; ++j ){
				Xml::ConstAttribute a = vertex.attribute( j );
				RefString name( a.name() );
				if ( name == "position" ){
					a.getFloatValues( &v.p.x, 3 );
				}else if ( name == "normal" ){
					a.getFloatValues( &v.n.x, 3 );
				}else if ( name == "uv" ){
					a.getFloatValues( &v.t.x, 2 );
				}else if ( name == "color" ){
					//RGBA32形式カラーに変換
					float color[ 4 ];
					a.getFloatValues( color, 4 );
					unsigned c[ 4 ];
					for ( int i = 0; i < 4; ++i ){
						c[ i ] = static_cast< unsigned >( color[ i ] * 255.f + 0.5f );
						c[ i ] = ( c[ i ] < 0 ) ? 0 : c[ i ];
						c[ i ] = ( c[ i ] > 255 ) ? 255 : c[ i ];
					}
					//RGBAの順でファイルに書かれているわけだが、32bit形式だとARGBになる。注意。
					v.c = ( c[ 3 ] << 24 ) | ( c[ 0 ] << 16 ) | ( c[ 1 ] << 8 ) | c[ 2 ];
				} else if ( name == "skinweights" ) {
					a.getFloatValues( &v.w.x, 3 );
					// 3つしかくれないので、残りの１つは自前で計算
					float t = 1.f - ( v.w.x + v.w.y + v.w.z );
					v.w.w =  ( t > 0 ) ? t : 0;
				} else if ( name == "boneindeices" ) {
					int tmp[ 4 ];
					a.getIntValues( tmp, 4 );
					for ( int i = 0; i < 4; ++i ) {
						v.b[ i ] = 0xff & tmp[ i ];
					}
				}
			}
		}
		unlock();
		vertices = 0;
	}

	void updateSkin( const Vertex* src, const Matrix3x4* matrixPallete, int matrixNumber ) {
		Vertex* vertices = static_cast< Vertex* >( lock() );
		for ( int i = 0; i < mVertexNumber; ++i ) {
			const Vertex& v = src[ i ];
			Array< SkinInfo >& s = mSkinInfo[ i ];
			Vector4 px( 0 ), py( 0 ), pz( 0 );
			Vector3 nx( 0 ), ny( 0 ), nz( 0 );
			for ( int k = 0; k < s.size(); ++k ) {
				int idx = s[ i ].pallete;
				ASSERT( idx < matrixNumber );
				float weight = s[ i ].weight;
				const Matrix3x4& m = matrixPallete[ idx ];
				Matrix3x4 it;
				it.setInverse( m );
				it.transpose33();
				px.madd( &m.m11, weight );
				py.madd( &m.m21, weight );
				pz.madd( &m.m31, weight );
				nx.madd( &it.m11, weight );
				ny.madd( &it.m21, weight );
				nz.madd( &it.m31, weight );
			}
			Vertex& dst = vertices[ i ];
			dst.p.set( px * v.p, py * v.p, pz * v.p );
			dst.n.set( nx * v.n, ny * v.n, nz * v.n );
		}
		unlock();
		vertices = 0;
	}

	GLuint mObject; // バッファオブジェクトのID
	int mVertexNumber; // 頂点数
	VertexBuffer::Format mFormat;
	VertexBuffer::Managed mManaged;
	unsigned char* mBuffer;
	char* mName;

	struct SkinInfo {
		float weight;
		int pallete;
	};
	Array< SkinInfo >* mSkinInfo;

	typedef void ( *VOID_FUNC )();
	static const int VERTEX_SIZES[];
	static const VOID_FUNC SET_INPUT_LAYOUT[];

private:
	Impl( const Impl& ); // 禁止
	void operator=( const Impl& ); // 禁止
};

} // namespace Graphics
} // namespace NTEngine

#endif // INCLUDED_NTENGINE_GRAPHICS_VERTEXBUFFERIMPL_H
